﻿using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Collections.Generic;

namespace Sonce.Editor.Tools
{
    public class LineTool : IEditorTool
    {
        #region Members
        private bool isDragLine = true;
        private LineTypes lineType = LineTypes.Line;
        private OrtoLines ortoLine = OrtoLines.None;
        #endregion

        #region Constructors
        public LineTool()
        {
        }

        public LineTool(LineTypes type)
        {
            this.lineType = type;
        }

        public LineTool(LineTypes type, OrtoLines orto)
        {
            this.lineType = type;
            this.ortoLine = orto;
        }
        #endregion

        #region Properties
        /// <summary>
        /// Get or set true to draw first node on MouseDown and draw last node on MouseUp.
        /// Get or set false to draw first and last node on MouseDown.
        /// Polyline cannot be drag line.
        /// </summary>
        public bool IsDragLine
        {
            get
            {
                bool result = this.isDragLine;
                if (this.lineType == LineTypes.PolyLine)
                {
                    result = false;
                }
                return result;
            }
            set
            {
                this.isDragLine = value;
            }
        }

        public LineTypes LineType
        {
            get
            {
                return this.lineType;
            }
            set
            {
                this.lineType = value;
            }
        }

        public OrtoLines OrtoLine
        {
            get
            {
                return this.ortoLine;
            }
            set
            {
                this.ortoLine = value;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Extend line points to the editor's edges.
        /// </summary>
        /// <param name="point1">First point of original line.</param>
        /// <param name="point2">Second point of original line.</param>
        /// <returns>Returns points of extended line.</returns>
        protected Point[] GetExtendedLine(Point point1, Point point2)
        {
            Point ref1 = new Point(), ref2 = new Point();

            double k = (point2.Y - point1.Y) / (point2.X - point1.X);
            double n = point2.Y - k * point2.X;
            if (double.IsInfinity(k))
            {
                ref1 = new Point(point1.X, 0);
                ref2 = new Point(point2.X, this.Editor.Height);
            }
            else if (n > 0)
            {
                ref1 = new Point(0, n);
                ref2 = new Point(this.Editor.Width, k * this.Editor.Width + n);
            }
            else
            {
                ref1 = new Point(n, n * (k + 1));
                ref2 = new Point(this.Editor.Width, k * this.Editor.Width + n);
            }

            return new Point[] { ref1, ref2 };
        }
        #endregion

        #region IEditorTool Members
        public Editor Editor
        {
            get;
            set;
        }

        public bool KeyDown(Key key)
        {
            return false;
        }

        public bool KeyUp(Key key)
        {
            return false;
        }

        public bool MouseDown(Point position)
        {
            switch (this.lineType)
            {
                case LineTypes.Line:
                    break;

                case LineTypes.PolyLine:
                    this.Editor.LayerPreview.Clear();
                    if (!this.Editor.IsDrawingPolyline)
                    {
                        Path path = StartPolyLine(this.Editor.LayerWorking, position);
                        path.Tag = new LinkTag();
                        this.Editor.IsDrawingPolyline = true;
                    }
                    else
                    {
                        ContinuePolyLine(this.Editor.LayerWorking, position);
                    }
                    break;

                case LineTypes.RefLine:
                    break;
            }

            return true;
        }

        public bool MouseUp(Point position)
        {
            switch (this.lineType)
            {
                case LineTypes.Line:
                    if (this.isDragLine)
                    {
                        DrawLine(this.Editor.LayerWorking, this.Editor.StartPoint, position);
                    }
                    break;

                case LineTypes.PolyLine:
                    break;

                case LineTypes.RefLine:
                    if (this.isDragLine)
                    {
                        this.Editor.LayerPreview.Clear();
                        DrawPolyLine(this.Editor.LayerWorking, GetExtendedLine(this.Editor.StartPoint, position));
                    }
                    break;
            }

            this.Editor.SelectionManager.UnselectAll();

            return true;
        }

        public bool MouseMove(Point position, bool isMouseDown)
        {
            switch (this.lineType)
            {
                case LineTypes.Line:
                    if (isMouseDown)
                    {
                        DrawLine(this.Editor.LayerPreview, this.Editor.StartPoint, position);
                    }
                    break;

                case LineTypes.PolyLine:
                    if (this.Editor.IsDrawingPolyline)
                    {
                        DrawLine(this.Editor.LayerPreview, this.Editor.StartPoint, position);
                    }
                    break;

                case LineTypes.RefLine:
                    if (isMouseDown)
                    {
                        DrawPolyLine(this.Editor.LayerPreview, GetExtendedLine(this.Editor.StartPoint, position));
                    }
                    break;
            }

            return true;
        }

        public bool MouseEnter()
        {
            return false;
        }

        public bool MouseLeave()
        {
            return false;
        }

        public bool MouseWheel(int delta)
        {
            return false;
        }
        #endregion

        #region Polyline drawing
        private Dictionary<Layer, Path> polyline = new Dictionary<Layer, Path>();
        private Dictionary<Layer, PathFigure> polyFigure = new Dictionary<Layer, PathFigure>();
        private Dictionary<Layer, PathGeometry> polyGeometry = new Dictionary<Layer, PathGeometry>();

        /// <summary>
        /// Draw a polyline defined by points.
        /// </summary>
        /// <param name="points"></param>
        public Path DrawPolyLine(Layer layer, Point[] points)
        {
            if (points == null || points.Length < 2)
            {
                throw new Exception("Polyline must have defined at least two points.");
            }

            polyline[layer] = new Path();
            polyline[layer].Stroke = layer.StrokeBrush;
            polyline[layer].StrokeThickness = 1.0;

            PathFigure polyFigure = new PathFigure();
            polyFigure.StartPoint = points[0];
            PathGeometry polyGeometry = new PathGeometry();
            polyGeometry.Figures.Add(polyFigure);
            polyline[layer].Data = polyGeometry;

            int count = points.Length;
            for (int i = 1; i < count; i++)
            {
                LineSegment segment = new LineSegment();
                segment.Point = points[i];
                polyFigure.Segments.Add(segment);
            }

            layer.Draw(polyline[layer]);

            return polyline[layer];
        }

        /// <summary>
        /// Start drawing a polyline at the specified location.
        /// </summary>
        /// <param name="point">Starting point of the polyline.</param>
        /// <remarks>VisualObjectDrawn event is triggered in this method.</remarks>
        public Path StartPolyLine(Layer layer, Point point)
        {
            this.polyline[layer] = new Path();
            this.polyline[layer].Stroke = layer.StrokeBrush;
            this.polyline[layer].StrokeThickness = 1.0;

            this.polyFigure[layer] = new PathFigure();
            this.polyFigure[layer].StartPoint = point;
            this.polyGeometry[layer] = new PathGeometry();
            this.polyGeometry[layer].Figures.Add(this.polyFigure[layer]);
            this.polyline[layer].Data = this.polyGeometry[layer];

            layer.Draw(this.polyline[layer]);

            return this.polyline[layer];
        }

        /// <summary>
        /// Draw a polyline node at the specified location.
        /// </summary>
        /// <param name="point">Polyline node location.</param>
        public void ContinuePolyLine(Layer layer, Point point)
        {
            LineSegment segment = new LineSegment();
            segment.Point = point;
            this.polyFigure[layer].Segments.Add(segment);
        }

        /// <summary>
        /// Stop drawing the polyline.
        /// </summary>
        /// <param name="connect">True to connect start and end node with a line, false to leave it opened.</param>
        public void ClosePolyLine(Layer layer, bool connect)
        {
            this.polyFigure[layer].IsClosed = connect;
        }

        /// <summary>
        /// Overloaded. Draw a line starting at the first point and ending at the second point.
        /// </summary>
        /// <param name="point1">Starting location of the line.</param>
        /// <param name="point2">Ending location of the line.</param>
        /// <returns>Returns the drawn visual object for further customization.</returns>
        /// <remarks>Technically the line is Path and can have more than two nodes if broken.</remarks>
        public Path DrawLine(Layer layer, Point point1, Point point2)
        {
            string data = string.Format("M{0},{1} L{2},{3}", point1.X, point1.Y, point2.X, point2.Y);
            Path path = Helper.CreatePath(data);
            path.Stroke = layer.StrokeBrush;
            path.StrokeThickness = 1.0;
            path.HorizontalAlignment = HorizontalAlignment.Left;
            path.VerticalAlignment = VerticalAlignment.Top;
            layer.Draw(path);
            return path;
        }

        /// <summary>
        /// Draw a line starting at the first point and ending at the second point.
        /// </summary>
        /// <param name="point1">Starting location of the line.</param>
        /// <param name="point2">Ending location of the line.</param>
        /// <param name="ortoLine">Line breaking type.</param>
        /// <returns>Returns the drawn visual object for futher customization.</returns>
        /// <remarks>Technically the line is Path and can have more than two nodes if broken.</remarks>
        public Path DrawLine(Layer layer, Point point1, Point point2, OrtoLines ortoLine)
        {
            Point[] points = Helper.GetOrtoLine(point1, point1, ortoLine);
            Path path = DrawPolyLine(layer, points);
            path.Stroke = layer.StrokeBrush;
            path.StrokeThickness = 1.0;
            path.HorizontalAlignment = HorizontalAlignment.Left;
            path.VerticalAlignment = VerticalAlignment.Top;
            layer.Draw(path);
            return path;
        }
        #endregion
    }
}
